package cn.jolyoulu.common.poi.excel.reader.sax;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xssf.model.SharedStrings;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import cn.jolyoulu.common.poi.excel.reader.handler.ExcelReaderHandler;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @Author: JolyouLu
 * @Date: 2023/12/16 21:59
 * @Description Sax模式下读取sheet的Handler
 */
public class SaxSheetReaderHandler extends DefaultHandler {
    /**
     * excel中共享的字符串xml
     */
    private final SharedStrings sst;
    /**
     * 处理每一个单元格的Handler
     */
    private ExcelReaderHandler<String> cellHandler;
    /**
     * 行
     */
    private Integer row;
    /**
     * 列
     */
    private Integer column;
    /**
     * 单元格类型
     */
    private CellType cellType;
    /**
     * 单元格内容
     */
    private String value;
    /**
     * 对excel中共享的字符串xml的缓存
     */
    private final LruCache<Integer, String> lruCache = new LruCache<>(50);

    private static class LruCache<A, B> extends LinkedHashMap<A, B> {
        private final int maxEntries;

        public LruCache(final int maxEntries) {
            super(maxEntries + 1, 1.0f, true);
            this.maxEntries = maxEntries;
        }

        @Override
        protected boolean removeEldestEntry(final Map.Entry<A, B> eldest) {
            return super.size() > maxEntries;
        }
    }

    public SaxSheetReaderHandler(SharedStrings sst, ExcelReaderHandler<String> cellHandler) {
        this.sst = sst;
        this.cellHandler = cellHandler;
    }

    /**
     * 当读取到开始的标签会触发这个方法
     * 例: <c><v>abc<v/><c/> 顺序 <c> => <v>
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        value = "";
        if (qName.equals("row")) {
            row = Integer.valueOf(attributes.getValue("r"));
        }
        if (qName.equals("c")) {
            column = parseColumn(attributes.getValue("r"));
            cellType = parseCellType(attributes.getValue("t"));
        }
    }

    /**
     * 当读取到结束的标签会触发这个方法
     * 例: <c><v>abc<v/><c/> 顺序 <v/> => <c/>
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if (Objects.isNull(row) || Objects.isNull(column) || Objects.isNull(cellType)) {
            return;
        }
        if (qName.equals("v")) {
            switch (this.cellType) {
                case STRING: //string类型需要去缓存表找数据
                    Integer idx = Integer.valueOf(value);
                    value = lruCache.get(idx);
                    if (Objects.isNull(value) && !lruCache.containsKey(idx)) {
                        value = sst.getItemAt(idx).getString();
                        lruCache.put(idx, value);
                    }
                    break;
                case UNKNOWN:
                    break;
            }
            cellHandler.handler(row, column, value);
        }
        if (qName.equals("row")) {
            row = null;
        }
        if (qName.equals("c")) {
            column = null;
        }
    }

    /**
     * 当读取到值时会触发这个方法
     * 例: <c><v>abc<v/><c/> 顺序 <v>abc<v/>
     */
    @Override
    public void characters(char[] ch, int start, int length) {
        value += new String(ch, start, length);
    }

    /**
     * 计算当前给与的列
     * A=1，AA=27，AAA=53
     *
     * @param r
     * @return
     */
    private Integer parseColumn(String r) {
        if (StringUtils.isBlank(r)) {
            return null;
        }
        String columnStr = r.replaceAll("[0-9]", "");
        int len = columnStr.length();
        char last = columnStr.charAt(len - 1);
        if (len == 1) {
            return last - 'A' + 1;
        } else {
            return (26 * (len - 1)) + (last - 'A' + 1);
        }
    }

    /**
     * 判断xml的单元格的类型
     *
     * @return
     */
    private CellType parseCellType(String t) {
        if (StringUtils.isBlank(t)) {
            return CellType.UNKNOWN;
        }
        switch (t) {
            case "s":
                return CellType.STRING;
            default:
                return CellType.UNKNOWN;
        }
    }

    private enum CellType {
        STRING, UNKNOWN
    }
}
